# Execute SQL and Read Query Results

Use the AI Studio SDK to submit SQL statements in a specified database and read statement outputs using the server-returned query record. When SQL needs to be preserved, create workbooks and versions; workbooks and queries are distinct resources.

(sdk-ai-studio-sql-flow)=
## Task workflow

1. Select target database and prepare SQL text; read database and table metadata first if needed.
2. After submitting SQL, save the server-returned query ID.
3. Read the query record to extract the statement ID.
4. Use the statement ID to read query results, details, or execution profiles.

Query IDs and statement IDs are not interchangeable. You can only read query results when the query record contains a statement ID.

(sdk-ai-studio-sql-prepare)=
## Prerequisites

| Required item | Role on this page |
| --- | --- |
| Client context bound to target workspace | Define workspace scope for SQL operations. |
| Target database name | Specify the execution target for SQL statements. |
| SQL text | Submit queries or save workbook versions. |
| Optional workbook name | Create workbooks when saving SQL. |

Mutation, deletion, and DDL statements permanently alter data. Verify SQL text, target workspace, database, and permissions prior to execution.

(sdk-ai-studio-sql-run)=
## Submit SQL and read results

The example creates a workbook version first, then executes SQL. Submission returns a query ID, not result rows; the query record provides the statement ID, which is used to read results.

:::::{tab-set}
:sync-group: sdk-language

::::{tab-item} Go
:sync: go

```go
import (
	"context"
	"fmt"

	sdk "github.com/matrixorigin/matrixflow/sdk/go-sdk"
)

func runSQL(ctx context.Context, workspace *sdk.WorkspaceHandle, databaseName, workbookName, sqlText string) error {
	metadata := workspace.SQLMetadata()
	database, err := metadata.Database(databaseName)
	if err != nil {
		return err
	}
	if _, err := database.Tables(ctx); err != nil {
		return err
	}

	workbook, createdWorkbook, err := workspace.CreateSQLWorkbook(ctx, workbookName)
	if err != nil {
		return err
	}
	if workbook.ID() != createdWorkbook.GetWorkbookId() {
		return fmt.Errorf("workbook handle and result do not match")
	}
	version, createdVersion, err := workbook.CreateVersion(ctx, sqlText)
	if err != nil {
		return err
	}
	if version.ID() != createdVersion.GetId() {
		return fmt.Errorf("workbook version handle and result do not match")
	}
	if err := version.Save(ctx); err != nil {
		return err
	}

	query, started, err := workspace.ExecuteSQL(
		ctx, sqlText, sdk.WithSQLQueryDBName(databaseName), sdk.WithSQLQueryLimit(100),
	)
	if err != nil {
		return err
	}
	if query.ID() != started.GetQueryId() {
		return fmt.Errorf("query handle and result do not match")
	}
	described, err := query.Describe(ctx)
	if err != nil {
		return err
	}
	if described.GetStatementId() == "" {
		return fmt.Errorf("query %q has no statement id", query.ID())
	}
	statement, err := workspace.SQLStatement(described.GetStatementId())
	if err != nil {
		return err
	}
	result, err := statement.Result(ctx, sdk.WithSQLResultLimit(100))
	if err != nil {
		return err
	}
	_ = result
	return nil
}
```

::::

::::{tab-item} Python
:sync: python

```python
import moi_product_sdk as sdk


def run_sql(workspace, database_name, workbook_name, sql_text):
    metadata = workspace.sql_metadata()
    database = metadata.database(database_name)
    database.tables()

    workbook, created_workbook = workspace.create_sql_workbook(workbook_name)
    if workbook.id != created_workbook.workbook_id:
        raise RuntimeError("workbook handle and result do not match")
    version, created_version = workbook.create_version(sql_text)
    if version.id != created_version.id:
        raise RuntimeError("workbook version handle and result do not match")
    version.save()

    query, started = workspace.execute_sql(
        sql_text,
        sdk.with_sql_query_db_name(database_name),
        sdk.with_sql_query_limit(100),
    )
    if query.id != started.query_id:
        raise RuntimeError("query handle and result do not match")
    described = query.describe()
    if not described.statement_id:
        raise RuntimeError(f"query {query.id} has no statement id")
    statement = workspace.sql_statement(described.statement_id)
    return statement.result(sdk.with_sql_result_limit(100))
```

::::

:::::

The result object contains pagination metadata and output rows for this read. When execution history or query profiles need inspection, continue using the same statement ID to retrieve the data.

(sdk-ai-studio-sql-limitations)=
## Limitations

- Workbooks, queries, and statements all require existing IDs; empty IDs are rejected by the client.
- Cancelling queries is an independent operation. Cancelling already completed queries may be rejected; read the query record first to confirm status.
- Dropping databases, tables, views, or workbooks affects the underlying objects; do not reuse original objects after workbook deletion.

(sdk-ai-studio-sql-next)=
## Next steps

- For HTTP endpoints managing SQL, workbooks, and SQL history, refer to the data processing endpoints in the API Reference.
